#include "ccCommandLineParser.h"

//CCLib
#include <CloudSamplingTools.h>

//qCC_db
#include <ccPointCloud.h>
#include <ccGenericMesh.h>
#include <ccProgressDialog.h>

//qCC
#include "fileIO/FileIOFilter.h"
#include "fileIO/BundlerFilter.h"
#include <ui_commandLineDlg.h>
#include "ccConsole.h"

//Qt
#include <QMessageBox>
#include <QDialog>
#include <QFileInfo>
#include <QDateTime>
#include <QElapsedTimer>

int ccCommandLineParser::Parse(int nargs, char** args)
{
	if (!args || nargs<2)
	{
		assert(false);
		return EXIT_SUCCESS;
	}

	QDialog consoleDlg;

	bool silent = (QString(args[1]).toUpper() == "-SILENT");
	if (!silent)
	{
		Ui_commandLineDlg commandLineDlg;
		commandLineDlg.setupUi(&consoleDlg);
		consoleDlg.show();
		ccConsole::Init(commandLineDlg.consoleWidget,&consoleDlg);
	}


	int result = ccCommandLineParser().parse(nargs,args,silent);

	if (!silent)
	{
		if (result == EXIT_SUCCESS)
			QMessageBox::information(&consoleDlg,"Processed finished","Job done");
		else
			QMessageBox::warning(&consoleDlg,"Processed finished","An error occured! Check console");
	}

	ccConsole::ReleaseInstance();

	return result;
}

ccCommandLineParser::ccCommandLineParser()
{
}

ccCommandLineParser::~ccCommandLineParser()
{
    removeClouds();
    removeMeshes();
}

void ccCommandLineParser::removeClouds()
{
	while (!m_clouds.empty())
	{
		delete m_clouds.back().first;
		m_clouds.pop_back();
	}
}

void ccCommandLineParser::removeMeshes()
{
	while (!m_meshes.empty())
	{
		delete m_meshes.back().first;
		m_meshes.pop_back();
	}
}

int ccCommandLineParser::Error(const QString& message)
{
	ccConsole::Error(qPrintable(message));
	//printf("[ERROR] %s\n",qPrintable(message));

	return EXIT_FAILURE;
}

QString ccCommandLineParser::Export2BIN(ccPointCloud* cloud, const QString& originalFilename, const QString& suffix)
{
    assert(cloud);
	ccHObject group;
	group.addChild(cloud,false);
	QFileInfo info(originalFilename);
	cloud->setName(qPrintable(QString("%1_%2").arg(cloud->getName() ? cloud->getName() : info.baseName()).arg(suffix)));
	QString outputFilename = QString("%1/%2_%3_%4.bin").arg(info.path()).arg(info.baseName()).arg(suffix).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh'h'mm"));
	if (FileIOFilter::SaveToFile(&group,qPrintable(outputFilename),BIN) != CC_FERR_NO_ERROR)
		return QString("Failed to save result in file '%1'").arg(outputFilename);

	Print(QString("--> result saved to file '%1'").arg(outputFilename));
    return QString();
}

void ccCommandLineParser::Print(const QString& message)
{
	ccConsole::Print(qPrintable(message));
	//printf("%s\n",qPrintable(message));
}

int ccCommandLineParser::parse(int nargs, char** args, bool silent)
{
	assert(nargs>0 && args);
	ccProgressDialog progressDlg;
	//ccProgressDialog* _progressDlg = (silent ? 0 : &progressDlg);
	ccProgressDialog* _progressDlg = &progressDlg;

	QElapsedTimer eTimer;
	eTimer.start();

	int i=0;
	while (i<nargs)
	{
		QString argument = QString(args[i]).toUpper();
		// "O" OPEN FILE
		if (argument == "-O")
		{
			if (++i==nargs)
				return Error("Missing parameter (filename after \"-O\")");

			//open specified file
			QString filename(args[i]);
			Print(QString("Opening file: '%1'").arg(filename));
			ccHObject* db = FileIOFilter::LoadFromUnknownFile(filename,false);
			if (!db)
				return Error(QString("Failed to open file '%1'").arg(filename));

			//look for clouds inside loaded DB
			std::vector<ccHObject*> clouds;
			db->filterChildren(clouds,false,CC_POINT_CLOUD);
			for (unsigned i=0;i<clouds.size();++i)
			{
				ccPointCloud* pc = static_cast<ccPointCloud*>(clouds[0]);
				pc->setFlagState(CC_FATHER_DEPENDANT,false);
				Print(QString("Found one cloud with %1 points").arg(pc->size()));
				m_clouds.push_back(std::pair<ccPointCloud*,QString>(pc,filename));
			}

			//look for meshes inside loaded DB
			std::vector<ccHObject*> meshes;
			db->filterChildren(meshes,false,CC_MESH);
			if (!meshes.empty())
			{
				ccGenericMesh* mesh = static_cast<ccGenericMesh*>(meshes[0]);
				mesh->setFlagState(CC_FATHER_DEPENDANT,false);
				Print(QString("Found one mesh with %1 faces and %2 vertices").arg(mesh->size()).arg(mesh->getAssociatedCloud()->size()));
				m_meshes.push_back(std::pair<ccGenericMesh*,QString>(mesh,filename));
			}

			delete db;
			db=0;
		}
		// "SS" SUBSAMPLING
		else if (argument == "-SS")
		{
			Print("[SUBSAMPLING]");
			if (m_clouds.empty())
				return Error("No point cloud to resample (be sure to open one with \"-O [cloud filename]\" before \"-SS\")");

			if (++i==nargs)
				return Error("Missing parameter (resampling method after \"-SS\")");

			QString method = QString(args[i]).toUpper();
			Print(QString("\tMethod: ")+method);
			if (method == "RANDOM")
			{
				if (++i==nargs)
					return Error("Missing parameter (number of points after \"-SS RANDOM\")");

				bool ok;
				unsigned count = QString(args[i]).toInt(&ok);
				if (!ok)
					return Error("Invalid number of points for random resampling!");
				Print(QString("\tOutput points: %1").arg(count));

				for (unsigned i=0;i<m_clouds.size();++i)
				{
					ccPointCloud* cloud = m_clouds[i].first;
					const QString& cloudFilename = m_clouds[i].second;
					Print(QString("\tProcessing cloud #%1 (%2)").arg(i+1).arg(cloud->getName() ? cloud->getName() : "no name"));

					CCLib::ReferenceCloud* refCloud = CCLib::CloudSamplingTools::subsampleCloudRandomly(cloud,count,_progressDlg);
					if (!refCloud)
						return Error("Subsampling process failed!");
					Print(QString("\tResult: %1 points").arg(refCloud->size()));

					//save output
					ccPointCloud result(refCloud,cloud);
					delete refCloud;
					refCloud=0;
					QString errorStr = Export2BIN(&result,cloudFilename,"RANDOM_SUBSAMPLED");
					if (!errorStr.isNull())
						return Error(errorStr);
				}

			}
			else if (method == "SPATIAL")
			{
				if (++i==nargs)
					return Error("Missing parameter (spatial step after \"-SS SPATIAL\")");

				bool ok;
				double step = QString(args[i]).toDouble(&ok);
				if (!ok || step<=0.0)
					return Error("Invalid step value for spatial resampling!");
				Print(QString("\tSpatial step: %1").arg(step));

				for (unsigned i=0;i<m_clouds.size();++i)
				{
					ccPointCloud* cloud = m_clouds[i].first;
					const QString& cloudFilename = m_clouds[i].second;
					Print(QString("\tProcessing cloud #%1 (%2)").arg(i+1).arg(cloud->getName() ? cloud->getName() : "no name"));

					CCLib::ReferenceCloud* refCloud = CCLib::CloudSamplingTools::resampleCloudSpatially(cloud,step,0,_progressDlg);
					if (!refCloud)
						return Error("Subsampling process failed!");
					Print(QString("\tResult: %1 points").arg(refCloud->size()));

					//save output
					ccPointCloud result(refCloud,cloud);
					delete refCloud;
					refCloud=0;
					QString errorStr = Export2BIN(&result,cloudFilename,"SPATIAL_SUBSAMPLED");
					if (!errorStr.isNull())
						return Error(errorStr);
				}
			}
			else if (method == "OCTREE")
			{
				if (++i==nargs)
					return Error("Missing parameter (octree level after \"-SS OCTREE\")");

				bool ok;
				int octreeLevel = QString(args[i]).toInt(&ok);
				if (!ok || octreeLevel<1 || octreeLevel>CCLib::DgmOctree::MAX_OCTREE_LEVEL)
					return Error("Invalid octree level!");
				Print(QString("\tOctree level: %1").arg(octreeLevel));

				for (unsigned i=0;i<m_clouds.size();++i)
				{
					ccPointCloud* cloud = m_clouds[i].first;
					const QString& cloudFilename = m_clouds[i].second;
					Print(QString("\tProcessing cloud #%1 (%2)").arg(i+1).arg(cloud->getName() ? cloud->getName() : "no name"));

					CCLib::ReferenceCloud* refCloud = CCLib::CloudSamplingTools::subsampleCloudWithOctreeAtLevel(cloud,octreeLevel,CCLib::CloudSamplingTools::NEAREST_POINT_TO_CELL_CENTER,_progressDlg);
					if (!refCloud)
						return Error("Subsampling process failed!");
					Print(QString("\tResult: %1 points").arg(refCloud->size()));

					//save output
					ccPointCloud result(refCloud,cloud);
					delete refCloud;
					refCloud=0;
					QString errorStr = Export2BIN(&result,cloudFilename,QString("OCTREE_LEVEL_%1_SUBSAMPLED").arg(octreeLevel));
					if (!errorStr.isNull())
						return Error(errorStr);
				}
			}
			else
			{
				return Error("Unknown method!");
			}
		}
		else if (argument == "-BUNDLER_IMPORT") //Import Bundler file + orthorectification
		{
			if (++i==nargs)
				return Error("Missing parameter (filename after \"-BUNDLER_IMPORT\")");

			//open specified file
			QString bundlerFilename(args[i]);
			Print(QString("Importing Bundler file: '%1'").arg(bundlerFilename));
			//ccHObject* db = FileIOFilter::LoadFromUnknownFile(filename,false);
			//if (!db)
			//	return Error(QString("Failed to open file '%1'").arg(filename));

			QString altKeypointsFilename;
			bool undistortImages = false;
			bool generateColoredDTM = false;
			unsigned coloredDTMVerticesCount = 0;
			float scaleFactor = 1.0f;

			//inner loop for Bundler import options
			while (i+1<nargs)
			{
				QString argument = QString(args[i+1]).toUpper();
				if (argument == "-ALT_KEYPOINTS")
				{
					++i; //local option confirmed, we can move on
					if (++i==nargs)
						return Error("Missing parameter (filename after \"-ALT_KEYPOINTS\")");
					altKeypointsFilename = QString(args[i]);
				}
				else if (argument == "-SCALE_FACTOR")
				{
					++i; //local option confirmed, we can move on
					if (++i==nargs)
						return Error("Missing parameter (value after \"-SCALE_FACTOR\")");
					bool conversionOk=false;
					scaleFactor = QString(args[i]).toFloat(&conversionOk);
					if (!conversionOk)
						return Error("Invalid parameter (value after \"-SCALE_FACTOR\")");
				}
				else if (argument == "-UNDISTORT")
				{
					++i; //local option confirmed, we can move on
					undistortImages=true;
				}
				else if (argument == "-COLOR_DTM")
				{
					++i; //local option confirmed, we can move on
					if (++i==nargs)
						return Error("Missing parameter (vertices count after \"-COLOR_DTM\")");
					bool conversionOk=false;
					coloredDTMVerticesCount = QString(args[i]).toUInt(&conversionOk);
					if (!conversionOk)
						return Error("Invalid parameter (vertices count after \"-COLOR_DTM\")");
					generateColoredDTM=true;
				}
				else
				{
					break; //as soon as we encounter an unrecognized argument, we break the local loop to go back on the main one!
				}
				++i;
			}

			BundlerFilter bf;
			ccHObject tempContainer;
			bf.loadFileExtended(qPrintable(bundlerFilename),tempContainer,false,altKeypointsFilename,undistortImages,generateColoredDTM,coloredDTMVerticesCount,scaleFactor);
		
		}
		else if (argument == "-CLEAR_CLOUDS")
		{
            removeClouds();
		}
		else if (argument == "-CLEAR_MESHES")
		{
            removeMeshes();
		}
		else if (argument == "-CLEAR")
		{
            removeClouds();
            removeMeshes();
		}

		++i;
	}

	ccConsole::Print("Processed finished in %.2f s.",eTimer.elapsed()/1.0e3);

	return EXIT_SUCCESS;
}
